#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
北交所数据抓取脚本
功能：
1. 获取北交所股票信息
2. 获取股本变动数据（过去5年）
3. 获取K线数据（过去5年，前复权）
4. 获取基本面数据时间序列（PE、PB、PS、市值等，过去5年每一天的数据）
支持断点续传
"""

import requests
import sqlite3
import pandas as pd
import json
import time
import logging
from datetime import datetime, timedelta
from typing import List, Dict, Optional
import os

# 配置
TOKEN = "4bb4c74b-3168-4d7a-ae2e-d679a32c7921"
DB_NAME = "bse_data.db"
API_BASE_URL = "https://open.lixinger.com/api/cn"
REQUEST_DELAY = 0.1  # API请求间隔，避免频率限制

# 日志配置
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('../bse_data_fetch.log', encoding='utf-8'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)


class BSEDataFetcher:
    """北交所数据抓取器"""

    def __init__(self, db_path: str = DB_NAME):
        self.db_path = db_path
        self.session = requests.Session()
        self.session.headers.update({
            'Content-Type': 'application/json',
            'User-Agent': 'BSE-Data-Fetcher/1.0'
        })

        # 计算5年日期范围
        self.end_date = datetime.now().strftime('%Y-%m-%d')
        self.start_date = (datetime.now() - timedelta(days=365*5)).strftime('%Y-%m-%d')

        logger.info(f"数据抓取时间范围: {self.start_date} 至 {self.end_date}")
        self.init_database()

    def init_database(self):
        """初始化数据库表结构"""
        try:
            conn = sqlite3.connect(self.db_path)
            cursor = conn.cursor()

            # 股票信息表
            cursor.execute('''
                CREATE TABLE IF NOT EXISTS stock_info (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    name TEXT NOT NULL,
                    stock_code TEXT UNIQUE NOT NULL,
                    area_code TEXT,
                    market TEXT,
                    exchange TEXT,
                    fs_table_type TEXT,
                    mutual_markets TEXT,
                    ipo_date DATE,
                    delisted_date DATE,
                    listing_status TEXT,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                )
            ''')

            # 股本变动表
            cursor.execute('''
                CREATE TABLE IF NOT EXISTS equity_change (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    stock_code TEXT NOT NULL,
                    date DATE NOT NULL,
                    declaration_date DATE,
                    change_reason TEXT,
                    capitalization REAL,
                    outstanding_shares_a REAL,
                    limited_shares_a REAL,
                    outstanding_shares_h REAL,
                    capitalization_change_ratio REAL,
                    outstanding_shares_a_change_ratio REAL,
                    limited_shares_a_change_ratio REAL,
                    outstanding_shares_h_change_ratio REAL,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    FOREIGN KEY (stock_code) REFERENCES stock_info(stock_code),
                    UNIQUE(stock_code, date)
                )
            ''')

            # K线数据表
            cursor.execute('''
                CREATE TABLE IF NOT EXISTS candlestick (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    stock_code TEXT NOT NULL,
                    date DATE NOT NULL,
                    open REAL,
                    close REAL,
                    high REAL,
                    low REAL,
                    volume REAL,
                    amount REAL,
                    change_ratio REAL,
                    to_r REAL,
                    complex_factor REAL,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    FOREIGN KEY (stock_code) REFERENCES stock_info(stock_code),
                    UNIQUE(stock_code, date)
                )
            ''')

            # 基本面数据表
            cursor.execute('''
                CREATE TABLE IF NOT EXISTS fundamental_data (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    stock_code TEXT NOT NULL,
                    date DATE NOT NULL,
                    pe_ttm REAL,
                    d_pe_ttm REAL,
                    pb REAL,
                    pb_wo_gw REAL,
                    ps_ttm REAL,
                    dyr REAL,
                    pcf_ttm REAL,
                    ev_ebit_r REAL,
                    ev_ebitda_r REAL,
                    ey REAL,
                    sp REAL,
                    spc REAL,
                    spa REAL,
                    tv REAL,
                    ta REAL,
                    to_r REAL,
                    shn REAL,
                    mc REAL,
                    mc_om REAL,
                    cmc REAL,
                    ecmc REAL,
                    ecmc_psh REAL,
                    fpa REAL,
                    fra REAL,
                    fnpa REAL,
                    fb REAL,
                    ssa REAL,
                    sra REAL,
                    snsa REAL,
                    sb REAL,
                    ha_sh REAL,
                    ha_shm REAL,
                    mm_nba REAL,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    FOREIGN KEY (stock_code) REFERENCES stock_info(stock_code),
                    UNIQUE(stock_code, date)
                )
            ''')

            # 创建索引
            cursor.execute('CREATE INDEX IF NOT EXISTS idx_equity_change_stock_code ON equity_change(stock_code)')
            cursor.execute('CREATE INDEX IF NOT EXISTS idx_equity_change_date ON equity_change(date)')
            cursor.execute('CREATE INDEX IF NOT EXISTS idx_candlestick_stock_code ON candlestick(stock_code)')
            cursor.execute('CREATE INDEX IF NOT EXISTS idx_candlestick_date ON candlestick(date)')
            cursor.execute('CREATE INDEX IF NOT EXISTS idx_fundamental_stock_code ON fundamental_data(stock_code)')
            cursor.execute('CREATE INDEX IF NOT EXISTS idx_fundamental_date ON fundamental_data(date)')

            conn.commit()
            conn.close()
            logger.info("数据库初始化完成")

        except Exception as e:
            logger.error(f"数据库初始化失败: {e}")
            raise

    def _make_request(self, url: str, data: Dict, max_retries: int = 3) -> Optional[Dict]:
        """发送API请求"""
        for attempt in range(max_retries):
            try:
                response = self.session.post(url, json=data, timeout=30)
                response.raise_for_status()

                result = response.json()
                # 检查API返回码，1表示成功
                if result.get('code') == 1:
                    return result.get('data')
                else:
                    logger.error(f"API返回错误: {result.get('message', '未知错误')}")
                    logger.debug(f"请求URL: {url}")
                    logger.debug(f"请求参数: {data}")
                    return None

            except requests.exceptions.RequestException as e:
                logger.warning(f"请求失败 (尝试 {attempt + 1}/{max_retries}): {e}")
                if attempt == max_retries - 1:  # 最后一次尝试时，记录详细错误
                    logger.error(f"请求最终失败: {url}")
                    logger.error(f"请求参数: {data}")
                    if hasattr(e, 'response') and e.response is not None:
                        try:
                            error_content = e.response.json()
                            logger.error(f"API错误响应: {error_content}")
                        except:
                            logger.error(f"HTTP响应内容: {e.response.text}")
                    return None
                else:
                    time.sleep(2 ** attempt)  # 指数退避

            time.sleep(REQUEST_DELAY)

    def fetch_stock_info(self) -> List[Dict]:
        """获取北交所股票信息"""
        logger.info("开始获取北交所股票信息...")

        url = f"{API_BASE_URL}/company"
        data = {
            "token": TOKEN,
            "market": "a"
        }

        result_data = self._make_request(url, data)
        if not result_data:
            return []

        # 过滤出北交所股票 (exchange字段为"bj")
        bse_stocks = []
        for stock in result_data:
            if stock.get('exchange') == 'bj':
                bse_stocks.append(stock)

        logger.info(f"获取到北交所股票 {len(bse_stocks)} 只")
        return bse_stocks

    def save_stock_info(self, stocks: List[Dict]):
        """保存股票信息到数据库"""
        if not stocks:
            return

        conn = sqlite3.connect(self.db_path)
        try:
            for stock in stocks:
                cursor = conn.cursor()
                cursor.execute('''
                    INSERT OR REPLACE INTO stock_info
                    (name, stock_code, area_code, market, exchange, fs_table_type,
                     mutual_markets, ipo_date, delisted_date, listing_status)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                ''', (
                    stock.get('name'),
                    stock.get('stockCode'),
                    stock.get('areaCode'),
                    stock.get('market'),
                    stock.get('exchange'),
                    stock.get('fsTableType'),
                    stock.get('mutualMarkets'),
                    stock.get('ipoDate'),
                    stock.get('delistedDate'),
                    stock.get('listingStatus')
                ))

            conn.commit()
            logger.info(f"保存股票信息 {len(stocks)} 条")

        except Exception as e:
            logger.error(f"保存股票信息失败: {e}")
            conn.rollback()
        finally:
            conn.close()

    def get_existing_stock_codes(self) -> set:
        """获取数据库中已存在的股票代码"""
        conn = sqlite3.connect(self.db_path)
        try:
            cursor = conn.cursor()
            cursor.execute("SELECT stock_code FROM stock_info")
            return {row[0] for row in cursor.fetchall()}
        finally:
            conn.close()

    def fetch_fundamental_data_all(self):
        """获取所有北交所股票过去5年的基本面数据（时间序列）"""
        logger.info("开始获取北交所股票5年基本面数据...")

        # 获取所有北交所股票代码
        stock_codes = self.get_existing_stock_codes()
        if not stock_codes:
            logger.error("没有找到北交所股票代码，请先获取股票信息")
            return

        stock_codes_list = list(stock_codes)
        total_stocks = len(stock_codes_list)
        logger.info(f"准备获取 {total_stocks} 只股票过去5年的基本面数据")

        all_fundamental_data = []
        failed_stocks = []

        # 逐个股票获取5年时间序列数据
        for i, stock_code in enumerate(stock_codes_list, 1):
            logger.info(f"处理股票 {i}/{total_stocks}: {stock_code}")

            # 获取该股票5年历史基本面数据
            fundamental_data = self.fetch_fundamental_data_time_range(stock_code, self.start_date, self.end_date)

            if fundamental_data:
                all_fundamental_data.extend(fundamental_data)
                logger.info(f"{stock_code} 获取5年数据: {len(fundamental_data)} 条")
            else:
                logger.warning(f"{stock_code} 未获取到基本面数据")
                failed_stocks.append(stock_code)

            # 每获取5只股票的数据就保存一次（防止内存占用过大）
            if len(all_fundamental_data) >= 5000 or (i == total_stocks and all_fundamental_data):
                self.save_fundamental_data(all_fundamental_data)
                logger.info(f"已保存 {len(all_fundamental_data)} 条基本面数据")
                all_fundamental_data = []  # 清空已保存的数据

            # 股票间延迟，避免API频率限制
            time.sleep(REQUEST_DELAY)

        # 保存剩余数据
        if all_fundamental_data:
            self.save_fundamental_data(all_fundamental_data)
            logger.info(f"最后保存 {len(all_fundamental_data)} 条基本面数据")

        # 输出统计信息
        logger.info("=" * 50)
        logger.info("基本面数据获取完成!")
        logger.info(f"成功股票: {total_stocks - len(failed_stocks)} 只")
        logger.info(f"失败股票: {len(failed_stocks)} 只")
        if failed_stocks:
            logger.warning(f"失败股票代码: {failed_stocks[:10]}...")  # 只显示前10个

        # 查询最终统计
        stats = self.get_data_count()
        logger.info(f"数据库中基本面数据总量: {stats['fundamental_data']} 条")
        logger.info("=" * 50)

    def fetch_fundamental_data_time_range(self, stock_code: str, start_date: str, end_date: str) -> List[Dict]:
        """获取单只股票指定时间范围内的基本面数据（时间序列）"""

        # 定义要获取的指标列表
        metrics_list = [
            'pe_ttm', 'pb', 'ps_ttm', 'dyr', 'pcf_ttm',
            'sp', 'spc', 'tv', 'ta', 'to_r',
            'shn', 'mc', 'cmc', 'ecmc', 'fb', 'sb'
        ]

        url = f"{API_BASE_URL}/company/fundamental/non_financial"
        data = {
            "token": TOKEN,
            "stockCodes": [stock_code],  # 注意：时间范围获取时只能传入1个股票代码
            "startDate": start_date,
            "endDate": end_date,
            "metricsList": metrics_list
        }

        result_data = self._make_request(url, data)
        return result_data if result_data else []

    def _get_latest_trading_date(self) -> str:
        """获取最近的交易日"""
        # 尝试几个最近的日期，找到有数据的交易日
        from datetime import datetime, timedelta

        test_dates = []
        for i in range(7):  # 测试最近7天
            date = (datetime.now() - timedelta(days=i)).strftime('%Y-%m-%d')
            test_dates.append(date)

        # 如果这些日期都不行，使用已知的有效日期
        test_dates.append('2025-10-28')  # 从测试中知道这个日期有效

        for date in test_dates:
            try:
                # 用一只测试股票检查该日期是否有数据
                test_stocks = list(self.get_existing_stock_codes())[:1]
                if test_stocks:
                    result = self.fetch_fundamental_data_batch_by_date(test_stocks, date)
                    if result:
                        logger.info(f"找到有效交易日: {date}")
                        return date
            except Exception as e:
                logger.debug(f"日期 {date} 测试失败: {e}")
                continue

        # 如果都失败了，返回默认日期
        logger.warning("未能找到有效交易日，使用默认日期")
        return '2025-10-28'

    def fetch_fundamental_data_batch_by_date(self, stock_codes: List[str], date: str = None) -> List[Dict]:
        """按指定日期批量获取多只股票的基本面数据"""
        if not stock_codes:
            return []

        if date is None:
            date = datetime.now().strftime('%Y-%m-%d')

        # 定义要获取的指标列表（选择最核心的指标，控制在API限制内）
        metrics_list = [
            'pe_ttm',      # PE-TTM
            'pb',          # PB
            'ps_ttm',      # PS-TTM
            'mc',          # 市值
            'cmc',         # 流通市值
            'ecmc',        # 自由流通市值
            'to_r',        # 换手率
            'tv',          # 成交量
            'ta',          # 成交金额
            'sp',          # 股价
            'spc',         # 涨跌幅
            'fb',          # 融资余额
            'sb',          # 融券余额
            'dyr',         # 股息率
            'pcf_ttm',     # PCF-TTM
            'shn'          # 总股东人数
        ]

        url = f"{API_BASE_URL}/company/fundamental/non_financial"
        data = {
            "token": TOKEN,
            "stockCodes": stock_codes,
            "date": date,
            "metricsList": metrics_list
        }

        result_data = self._make_request(url, data)
        return result_data if result_data else []

    def get_existing_fundamental_dates(self, stock_code: str) -> set:
        """获取某只股票已存在的基本面数据日期"""
        conn = sqlite3.connect(self.db_path)
        try:
            cursor = conn.cursor()
            cursor.execute("SELECT date FROM fundamental_data WHERE stock_code = ?", (stock_code,))
            return {row[0] for row in cursor.fetchall()}
        finally:
            conn.close()

    def fetch_equity_change(self, stock_code: str) -> List[Dict]:
        """获取单只股票的股本变动数据"""
        url = f"{API_BASE_URL}/company/equity-change"
        data = {
            "token": TOKEN,
            "stockCode": stock_code,
            "startDate": self.start_date,
            "endDate": self.end_date
        }

        result_data = self._make_request(url, data)
        return result_data if result_data else []

    def save_equity_change(self, stock_code: str, equity_data: List[Dict]):
        """保存股本变动数据"""
        if not equity_data:
            return

        conn = sqlite3.connect(self.db_path)
        try:
            for item in equity_data:
                cursor = conn.cursor()
                cursor.execute('''
                    INSERT OR IGNORE INTO equity_change
                    (stock_code, date, declaration_date, change_reason, capitalization,
                     outstanding_shares_a, limited_shares_a, outstanding_shares_h,
                     capitalization_change_ratio, outstanding_shares_a_change_ratio,
                     limited_shares_a_change_ratio, outstanding_shares_h_change_ratio)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                ''', (
                    stock_code,
                    item.get('date'),
                    item.get('declarationDate'),
                    item.get('changeReason'),
                    item.get('capitalization'),
                    item.get('outstandingSharesA'),
                    item.get('limitedSharesA'),
                    item.get('outstandingSharesH'),
                    item.get('capitalizationChangeRatio'),
                    item.get('outstandingSharesAChangeRatio'),
                    item.get('limitedSharesAChangeRatio'),
                    item.get('outstandingSharesHChangeRatio')
                ))

            conn.commit()
            logger.debug(f"保存 {stock_code} 股本数据 {len(equity_data)} 条")

        except Exception as e:
            logger.error(f"保存股本数据失败 {stock_code}: {e}")
            conn.rollback()
        finally:
            conn.close()

    def fetch_candlestick(self, stock_code: str) -> List[Dict]:
        """获取单只股票的K线数据（前复权）"""
        url = f"{API_BASE_URL}/company/candlestick"
        data = {
            "token": TOKEN,
            "stockCode": stock_code,
            "type": "fc_rights",  # 前复权
            "startDate": self.start_date,
            "endDate": self.end_date
        }

        result_data = self._make_request(url, data)
        return result_data if result_data else []

    def fetch_fundamental_data_batch(self, stock_codes: List[str], start_date: str = None, end_date: str = None) -> List[Dict]:
        """批量获取多只股票的基本面数据"""
        if not stock_codes:
            return []

        # 默认使用5年时间范围
        if start_date is None:
            start_date = self.start_date
        if end_date is None:
            end_date = self.end_date

        # 定义要获取的指标列表（选择最核心的指标，控制在API限制内）
        metrics_list = [
            'pe_ttm',      # PE-TTM
            'pb',          # PB
            'ps_ttm',      # PS-TTM
            'mc',          # 市值
            'cmc',         # 流通市值
            'ecmc',        # 自由流通市值
            'to_r',        # 换手率
            'tv',          # 成交量
            'ta',          # 成交金额
            'sp',          # 股价
            'spc',         # 涨跌幅
            'fb',          # 融资余额
            'sb',          # 融券余额
            'dyr',         # 股息率
            'pcf_ttm',     # PCF-TTM
            'shn'          # 总股东人数
        ]

        url = f"{API_BASE_URL}/company/fundamental/non_financial"
        data = {
            "token": TOKEN,
            "stockCodes": stock_codes,
            "startDate": start_date,
            "endDate": end_date,
            "metricsList": metrics_list
        }

        result_data = self._make_request(url, data)
        return result_data if result_data else []

    def fetch_fundamental_data_single(self, stock_code: str, date: str = None) -> List[Dict]:
        """获取单只股票指定日期的基本面数据"""
        if date is None:
            date = datetime.now().strftime('%Y-%m-%d')

        # 只获取核心指标，避免API限制
        metrics_list = [
            'pe_ttm', 'pb', 'ps_ttm', 'dyr', 'pcf_ttm',
            'sp', 'spc', 'tv', 'ta', 'to_r',
            'shn', 'mc', 'cmc', 'ecmc', 'fb', 'sb'
        ]

        url = f"{API_BASE_URL}/company/fundamental/non_financial"
        data = {
            "token": TOKEN,
            "stockCodes": [stock_code],
            "date": date,
            "metricsList": metrics_list
        }

        result_data = self._make_request(url, data)
        return result_data if result_data else []

    def save_candlestick(self, stock_code: str, candlestick_data: List[Dict]):
        """保存K线数据"""
        if not candlestick_data:
            return

        conn = sqlite3.connect(self.db_path)
        try:
            for item in candlestick_data:
                cursor = conn.cursor()
                cursor.execute('''
                    INSERT OR IGNORE INTO candlestick
                    (stock_code, date, open, close, high, low, volume, amount,
                     change_ratio, to_r, complex_factor)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                ''', (
                    stock_code,
                    item.get('date'),
                    item.get('open'),
                    item.get('close'),
                    item.get('high'),
                    item.get('low'),
                    item.get('volume'),
                    item.get('amount'),
                    item.get('change'),
                    item.get('to_r'),
                    item.get('complexFactor')
                ))

            conn.commit()
            logger.debug(f"保存 {stock_code} K线数据 {len(candlestick_data)} 条")

        except Exception as e:
            logger.error(f"保存K线数据失败 {stock_code}: {e}")
            conn.rollback()
        finally:
            conn.close()

    def save_fundamental_data(self, fundamental_data: List[Dict]):
        """保存基本面数据"""
        if not fundamental_data:
            return

        conn = sqlite3.connect(self.db_path)
        try:
            for item in fundamental_data:
                cursor = conn.cursor()
                cursor.execute('''
                    INSERT OR IGNORE INTO fundamental_data
                    (stock_code, date, pe_ttm, pb, ps_ttm, dyr, pcf_ttm, sp, spc,
                     tv, ta, to_r, shn, mc, cmc, ecmc, fb, sb)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                ''', (
                    item.get('stockCode'),
                    item.get('date'),
                    item.get('pe_ttm'),
                    item.get('pb'),
                    item.get('ps_ttm'),
                    item.get('dyr'),
                    item.get('pcf_ttm'),
                    item.get('sp'),
                    item.get('spc'),
                    item.get('tv'),
                    item.get('ta'),
                    item.get('to_r'),
                    item.get('shn'),
                    item.get('mc'),
                    item.get('cmc'),
                    item.get('ecmc'),
                    item.get('fb'),
                    item.get('sb')
                ))

            conn.commit()
            logger.debug(f"保存基本面数据 {len(fundamental_data)} 条")

        except Exception as e:
            logger.error(f"保存基本面数据失败: {e}")
            logger.error(f"数据项示例: {fundamental_data[0] if fundamental_data else 'None'}")
            conn.rollback()
        finally:
            conn.close()

    def get_data_count(self) -> Dict:
        """获取数据库中各表的数据量统计"""
        conn = sqlite3.connect(self.db_path)
        try:
            cursor = conn.cursor()

            cursor.execute("SELECT COUNT(*) FROM stock_info")
            stock_count = cursor.fetchone()[0]

            cursor.execute("SELECT COUNT(*) FROM equity_change")
            equity_count = cursor.fetchone()[0]

            cursor.execute("SELECT COUNT(*) FROM candlestick")
            candlestick_count = cursor.fetchone()[0]

            cursor.execute("SELECT COUNT(*) FROM fundamental_data")
            fundamental_count = cursor.fetchone()[0]

            return {
                'stocks': stock_count,
                'equity_changes': equity_count,
                'candlesticks': candlestick_count,
                'fundamental_data': fundamental_count
            }
        finally:
            conn.close()

    def run(self):
        """执行完整的数据抓取流程"""
        logger.info("开始北交所数据抓取...")
        start_time = datetime.now()

        try:
            # 1. 获取股票信息
            stocks = self.fetch_stock_info()
            if not stocks:
                logger.error("未能获取到股票信息，终止执行")
                return

            self.save_stock_info(stocks)
            logger.info(f"股票信息获取完成，共 {len(stocks)} 只股票")

            # 2. 获取股本变动和K线数据
            existing_stock_codes = self.get_existing_stock_codes()
            total_stocks = len(existing_stock_codes)

            for i, stock_code in enumerate(existing_stock_codes, 1):
                logger.info(f"处理股票 {i}/{total_stocks}: {stock_code}")

                # 股本变动数据
                equity_data = self.fetch_equity_change(stock_code)
                if equity_data:
                    self.save_equity_change(stock_code, equity_data)
                    logger.debug(f"{stock_code} 股本数据: {len(equity_data)} 条")

                # K线数据
                candlestick_data = self.fetch_candlestick(stock_code)
                if candlestick_data:
                    self.save_candlestick(stock_code, candlestick_data)
                    logger.debug(f"{stock_code} K线数据: {len(candlestick_data)} 条")

            # 3. 获取基本面数据
            logger.info("开始获取基本面数据...")
            self.fetch_fundamental_data_all()

            # 4. 输出统计信息
            end_time = datetime.now()
            duration = end_time - start_time
            stats = self.get_data_count()

            logger.info("=" * 50)
            logger.info("数据抓取完成!")
            logger.info(f"耗时: {duration}")
            logger.info(f"股票数量: {stats['stocks']}")
            logger.info(f"股本变动记录: {stats['equity_changes']}")
            logger.info(f"K线记录: {stats['candlesticks']}")
            logger.info(f"基本面数据记录: {stats['fundamental_data']}")
            logger.info(f"数据库文件: {os.path.abspath(self.db_path)}")
            logger.info("=" * 50)

        except Exception as e:
            logger.error(f"数据抓取过程中发生错误: {e}")
            raise


def main():
    """主函数"""
    fetcher = BSEDataFetcher()
    fetcher.run()


if __name__ == "__main__":
    main()